From 84d9e42b55962e4a05778a495a15e52de1a39b03 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 16 Jul 2025 11:08:00 -0400 Subject: [PATCH] Add root.transient-ro An example use case for this is having privileged code add dynamic new toplevel mountpoints (that don't persist across reboots/upgrades), while still keeping the rootfs readonly for processes by default. Closes: https://github.com/ostreedev/ostree/issues/3471 Signed-off-by: Colin Walters --- man/ostree-prepare-root.xml | 9 ++++ src/libotcore/otcore-prepare-root.c | 16 +++++++ src/libotcore/otcore.h | 4 ++ src/switchroot/ostree-prepare-root.c | 5 +++ .../kolainst/destructive/root-transient-ro.sh | 44 +++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100755 tests/kolainst/destructive/root-transient-ro.sh diff --git a/man/ostree-prepare-root.xml b/man/ostree-prepare-root.xml index 103312ec..8627886b 100644 --- a/man/ostree-prepare-root.xml +++ b/man/ostree-prepare-root.xml @@ -132,6 +132,15 @@ License along with this library. If not, see . + + root.transient-ro + A boolean value; the default is false. + This is like root.transient, but the overlayfs upper will be mounted + read-only by default. Use this when you want specific privileged components to be able to + write to the upper by temporarily mounting it writable in a new mount namespace. + + + composefs.enabled This can be yes, no, maybe, diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c index a52d711d..48ffc317 100644 --- a/src/libotcore/otcore-prepare-root.c +++ b/src/libotcore/otcore-prepare-root.c @@ -189,6 +189,18 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, OTCORE_PREPARE_ROOT_TRANSIENT_KEY, FALSE, &ret->root_transient, error)) return NULL; + if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, OTCORE_PREPARE_ROOT_TRANSIENT_RO_KEY, + FALSE, &ret->root_transient_ro, error)) + return NULL; + if (ret->root_transient && ret->root_transient_ro) + { + return glnx_null_throw (error, "Cannot set both root.transient and root.transient-ro"); + } + // This way callers can test for just root_transient + else if (ret->root_transient_ro) + { + ret->root_transient = TRUE; + } g_autofree char *enabled = g_key_file_get_value (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY, OTCORE_PREPARE_ROOT_ENABLED_KEY, NULL); @@ -451,6 +463,8 @@ otcore_mount_rootfs (RootConfig *rootfs_config, GVariantBuilder *metadata_builde /* Pass on the state */ g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT, g_variant_new_boolean (rootfs_config->root_transient)); + g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT_RO, + g_variant_new_boolean (rootfs_config->root_transient_ro)); bool using_composefs = FALSE; #ifdef HAVE_COMPOSEFS @@ -496,6 +510,8 @@ otcore_mount_rootfs (RootConfig *rootfs_config, GVariantBuilder *metadata_builde cfs_options.workdir = root_workdir; cfs_options.upperdir = root_upperdir; + if (rootfs_config->root_transient_ro) + cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY; } else { diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h index 456db9d6..0c7329d3 100644 --- a/src/libotcore/otcore.h +++ b/src/libotcore/otcore.h @@ -68,6 +68,7 @@ typedef struct { OtTristate composefs_enabled; gboolean root_transient; + gboolean root_transient_ro; gboolean require_verity; gboolean is_signed; char *signature_pubkey; @@ -132,6 +133,7 @@ gboolean otcore_mount_etc (GKeyFile *config, GVariantBuilder *metadata_builder, #define OTCORE_PREPARE_ROOT_ENABLED_KEY "enabled" #define OTCORE_PREPARE_ROOT_KEYPATH_KEY "keypath" #define OTCORE_PREPARE_ROOT_TRANSIENT_KEY "transient" +#define OTCORE_PREPARE_ROOT_TRANSIENT_RO_KEY "transient-ro" // For use with systemd soft reboots #define OTCORE_RUN_NEXTROOT "/run/nextroot" @@ -152,6 +154,8 @@ gboolean otcore_mount_etc (GKeyFile *config, GVariantBuilder *metadata_builder, #define OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE "composefs.signed" // This key will be present if the root is transient #define OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT "root.transient" +// This key will be present if the root is transient readonly +#define OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT_RO "root.transient-ro" // This key will be present if the sysroot-ro flag was found #define OTCORE_RUN_BOOTED_KEY_SYSROOT_RO "sysroot-ro" // Always holds the (device, inode) pair of the booted deployment diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index cf3bd1c9..c7df55d0 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -234,6 +234,11 @@ main (int argc, char *argv[]) const bool sysroot_currently_writable = !path_is_on_readonly_fs (root_arg); g_print ("sysroot.readonly configuration value: %d (fs writable: %d)\n", (int)sysroot_readonly, (int)sysroot_currently_writable); + if (rootfs_config->root_transient) + { + g_print ("root.transient: %d (ro: %d)\n", (int)rootfs_config->root_transient, + (int)rootfs_config->root_transient_ro); + } /* Remount root MS_PRIVATE here to avoid errors due to the kernel-enforced * constraint that disallows MS_SHARED mounts to be moved. diff --git a/tests/kolainst/destructive/root-transient-ro.sh b/tests/kolainst/destructive/root-transient-ro.sh new file mode 100755 index 00000000..7a8ab54a --- /dev/null +++ b/tests/kolainst/destructive/root-transient-ro.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -xeuo pipefail + +. ${KOLA_EXT_DATA}/libinsttest.sh + +prepare_tmpdir + +echo "testing boot=${AUTOPKGTEST_REBOOT_MARK:-}" + +# Print this by default on each boot +ostree admin status + +case "${AUTOPKGTEST_REBOOT_MARK:-}" in + "") + # xref https://github.com/coreos/coreos-assembler/pull/2814 + systemctl mask --now zincati + + test '!' -w / + + cp /usr/lib/ostree/prepare-root.conf /etc/ostree/ + cat >> /etc/ostree/prepare-root.conf <<'EOF' +[root] +transient-ro = true +EOF + + rpm-ostree initramfs-etc --track /etc/ostree/prepare-root.conf + + /tmp/autopkgtest-reboot "2" + ;; + "2") + + test '!' -w '/' + + unshare -m /bin/sh -c 'env LIBMOUNT_FORCE_MOUNT2=always mount -o remount,rw / && mkdir /new-dir-in-root' + test -d /new-dir-in-root + + test '!' -w '/' + + echo "ok root transient-ro" + ;; + *) + fatal "Unexpected AUTOPKGTEST_REBOOT_MARK=${AUTOPKGTEST_REBOOT_MARK}" + ;; +esac -- 2.30.2